/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.dependancy_measuring;

import moved.org.objectweb.asm.FieldVisitor;
import moved.org.objectweb.asm.Label;
import moved.org.objectweb.asm.MethodVisitor;

import org.objectweb.asm.Type;

import picounit.features.classloader.NullVisitor;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class DependancyMeasuringClassVisitor extends NullVisitor {
    private static final char STATIC_CLASS_SEPARATOR = '$';
    private static final String STATIC_CLASS_PREFIX = "class" + STATIC_CLASS_SEPARATOR;

    private final TypeParser typeParser = new TypeParser();

	///////////////////////////////////////////////////////////////////////////////////////////////
	// ClassVisitor methods
	///////////////////////////////////////////////////////////////////////////////////////////////
	
	@Override
	public void visit(int version, int access, String name, String signature, String superName,
		String[] interfaces) {

		dependancyEvent(toClassName(superName));

		if (interfaces != null) {
			for (String interfaze : interfaces) {
				dependancyEvent(toClassName(interfaze));
			}
		}
	}

	@Override
	public FieldVisitor visitField(int access, String name, String desc, String signature,
		Object value) {

		classDescEvent(desc);

		return this;
	}

	@Override
	public MethodVisitor visitMethod(int access, String name, String desc, String signature,
		String[] exceptions) {

		nonPrimativeDependancyEvent(Type.getReturnType(desc));

		for (Type argumentType : Type.getArgumentTypes(desc)) {
			nonPrimativeDependancyEvent(argumentType);
		}

		if (exceptions != null) {
			for (String exception : exceptions) {
				dependancyEvent(toClassName(exception));
			}
		}

		return this;
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// MethodVisitor methods
	///////////////////////////////////////////////////////////////////////////////////////////////

	@Override
	public void visitMethodInsn(int opcode, String owner, String name, String desc) {
		dependancyEvent(toClassName(owner));

		nonPrimativeDependancyEvent(Type.getReturnType(desc));

		for (Type argumentType : Type.getArgumentTypes(desc)) {
			nonPrimativeDependancyEvent(argumentType);
		}
	}

	@Override
	public void visitTypeInsn(int opcode, String desc) {
		dependancyEvent(toClassName(desc));
	}

    @Override
	public void visitFieldInsn(int opcode, String owner, String name, String desc) {
        if (name.startsWith(STATIC_CLASS_PREFIX)) {
        	String text = name.substring(STATIC_CLASS_PREFIX.length());

			dependancyEvent(text.replace(STATIC_CLASS_SEPARATOR, '.'));
        }
    }

    @Override
	public void visitLocalVariable(String name, String desc, String signature, Label start,
    	Label end, int index) {

		if (!"this".equals(name)) {
			Type type = typeParser.componentType(desc);

			if (!typeParser.isPrimative(type)) {
				dependancyEvent(typeParser.typeName(type));
			}
		}
    }

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Implementation
	///////////////////////////////////////////////////////////////////////////////////////////////

	private String toClassName(String owner) {
		return owner.replace('/', '.');
	}

	private void classDescEvent(String classDesc) {
		if (classDesc.startsWith("L") && classDesc.endsWith(";")) {
			String className = classDesc.substring(1, classDesc.length() - 1).replace('/', '.');

			dependancyEvent(className);
		}
	}

	private void nonPrimativeDependancyEvent(Type type) {
		if (!typeParser.isPrimative(type)) {
			dependancyEvent(typeParser.typeName(type));
		}
	}

	private void dependancyEvent(String className) {
		if (!className.startsWith("java") && !className.startsWith("javax")) {
			if (className.equals("int")) {
				new Throwable().printStackTrace();
			}
//			System.out.println(className);
		}
	}

	public static class TypeParser {
		private final Map<Type, String> typeToClassNameMap = new HashMap<Type, String>();

		public TypeParser() {
			mapTypeToClassName(Type.BOOLEAN_TYPE, boolean.class);
			mapTypeToClassName(Type.BYTE_TYPE, byte.class);
			mapTypeToClassName(Type.CHAR_TYPE, char.class);
			mapTypeToClassName(Type.DOUBLE_TYPE, double.class);
			mapTypeToClassName(Type.FLOAT_TYPE, float.class);
			mapTypeToClassName(Type.INT_TYPE, int.class);
			mapTypeToClassName(Type.LONG_TYPE, long.class);
			mapTypeToClassName(Type.SHORT_TYPE, short.class);
			mapTypeToClassName(Type.VOID_TYPE, void.class);
		}

		public String typeName(Type type) {
			return isPrimative(type) ? primativeTypeName(type) : ignoreArray(type).getClassName();
		}

		public String typeName(String desc) {
			return typeName(componentType(desc));
		}

		public Type componentType(String desc) {
			return ignoreArray(Type.getType(desc));
		}

		public String returnType(String desc) {
			return typeName(Type.getReturnType(desc));
		}

		public String[] parameterTypes(String desc) {
			List<String> methodParameters = new LinkedList<String>();

			for (Type type : Type.getArgumentTypes(desc)) {
				methodParameters.add(typeName(type));
			}

			return methodParameters.toArray(new String[methodParameters.size()]);
		}

		public boolean isPrimative(Type type) {
			return primativeTypeName(type) != null;
		}

		///////////////////////////////////////////////////////////////////////////////////////////
		// Implementation
		///////////////////////////////////////////////////////////////////////////////////////////

		private String primativeTypeName(Type type) {
			return typeToClassNameMap.get(ignoreArray(type));
		}

		private Type ignoreArray(Type type) {
			while (type.getSort() == Type.ARRAY) {
				type = type.getElementType();
			}

			return type;
		}

		private void mapTypeToClassName(Type type, Class typeClass) {
			typeToClassNameMap.put(type, typeClass.getName());
		}
	}
}
